package eduware.util;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Set;
import java.util.Vector;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import eduware.ctrl.Ctrl;
import eduware.util.crypt.EncryptedWriter;

/**
 * Enables you to handle the program's configuration via a XML file.<br>
 * Singleton
 * 
 * @author Florian Bausch
 */
public final class Config extends HashMap<String, String> {
	private static class Holder {
		public static final Config INSTANCE = new Config();
	}

	/**
	 * Auto generated serialVersionUID
	 */
	private static final long serialVersionUID = -3740632761998756639L;

	private boolean showConfigWindow = false;

	/**
	 * Get the Singleton instance of Config.
	 * 
	 * @return The instance of Config
	 */
	public static synchronized Config getInstance() {
		return Holder.INSTANCE;
	}

	/**
	 * Contains all registered, implemented Interfaces
	 */
	private Vector<WritesConfig> implementedInterfaces = new Vector<WritesConfig>();

	/**
	 * Creates a Config object that stores the configuration that is stored in
	 * the config file.
	 */
	private Config() {
		// Document doc = FileHandler.getEncryptedXMLDocument(
		// "config/config.eduware", 1000);
		// if (doc == null)
		// return;

		String path = (Ctrl.DEBUG_MODE) ? "config/config.eduware"
				: Ctrl.BASE_PATH + "config.eduware";
		Document doc = FileHandler.getEncryptedXMLDocument(path, 500);
		System.out.print(path);
		if (doc == null) {
			try {
				FileHandler.reloadXMLFile("config.eduware", path, true);
				doc = FileHandler.getXMLDocument(path, 500);
			} catch (FileNotFoundException e) {
				System.err.println("Could not load configuration file");
				showConfigWindow = true;
				return;
			}
		}

		NodeList list = doc.getDocumentElement().getElementsByTagName("entry");
		for (int i = 0; i < list.getLength(); i++) {
			Element node = (Element) list.item(i);
			this.put(node.getAttribute("key").trim(), node.getTextContent()
					.trim());
		}
	}

	/**
	 * Reads a setting from the settings list.
	 * 
	 * @param key
	 *            The specific key of this setting.
	 * @return A string representation of the setting.
	 */
	public synchronized String get(String key) {
		try {
			return super.get(key).replace("&lt;", "<").replace("&gt;", ">")
					.replace("&amp;", "&");
		} catch (Exception e) {
			return null;
		}
	}

	/**
	 * Returns the float representation of the value of a stored key, if it is
	 * possible. If not, the <code>defaultVal</code> is returned. <br>
	 * If the key does not exist, it is created with the value
	 * <code>defaultVal</code>.
	 * 
	 * @param key
	 *            The key of the value in the Config.
	 * @param defaultVal
	 *            The fall back value.
	 * @return The float representation of the value, or <code>defaultVal</code>
	 */
	public synchronized float getFloat(String key, float defaultVal) {
		String str = this.get(key);
		if (str == null) {
			this.put(key, "" + defaultVal);
			return defaultVal;
		}
		try {
			return Float.parseFloat(str);
		} catch (Exception e) {
			return defaultVal;
		}
	}

	/**
	 * Returns the int representation of the value of a stored key, if it is
	 * possible. If not, the <code>defaultVal</code> is returned. <br>
	 * If the key does not exist, it is created with the value
	 * <code>defaultVal</code>.
	 * 
	 * @param key
	 *            The key of the value in the Config.
	 * @param defaultVal
	 *            The fall back value.
	 * @return The int representation of the value, or <code>defaultVal</code>
	 */
	public synchronized int getInt(String key, int defaultVal) {
		String str = this.get(key);
		if (str == null) {
			this.put(key, "" + defaultVal);
			return defaultVal;
		}
		try {
			return Integer.parseInt(str);
		} catch (Exception e) {
			return defaultVal;
		}
	}

	/**
	 * Returns the String representation of the value of a stored key.<br>
	 * If the key does not exist, it is created with the value
	 * <code>defaultVal</code>.
	 * 
	 * @param key
	 *            The key of the value in the Config.
	 * @param defaultVal
	 *            The fall back value.
	 * @return The String representation of the value, or
	 *         <code>defaultVal</code>
	 */
	public synchronized String getString(String key, String defaultVal) {
		String str = this.get(key);
		if (str == null) {
			this.put(key, "" + defaultVal);
			return defaultVal;
		} else
			return str;
	}

	/**
	 * Checks whether there is a certain <code>key</code> stored in the config
	 * or not.
	 * 
	 * @param key
	 *            The key you want to check.
	 * @return <code>true</code> if it exists.
	 */
	public synchronized boolean keyExists(String key) {
		return !(this.get(key) == null);
	}

	/**
	 * Sets the value of a certain parameter in the config. When the config is
	 * written to a file the last value that was set for the <code>key</code> is
	 * stored.
	 * 
	 * @param key
	 *            A unique identification key for this setting.
	 * @param value
	 *            The value that shall be stored.
	 * @return
	 */
	public synchronized String put(String key, String value) {
		return super.put(key, value.replace("&", "&amp;").replace("<", "&lt;")
				.replace(">", "&gt;"));
	}

	/**
	 * Registers a class at Config.
	 * 
	 * @param wc
	 *            Class that implements WritesConfig
	 */
	public void registerImplementedInterface(WritesConfig wc) {
		this.implementedInterfaces.add(wc);
	}

	/**
	 * Writes the config to a file.
	 */
	public void writeConfig() {
		Iterator<WritesConfig> iter = this.implementedInterfaces.iterator();
		while (iter.hasNext())
			iter.next().writeConfig(this);

		try {
			EncryptedWriter writer = FileHandler
					.getEncryptedWriter("config/config.eduware");
			writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
			writer.write("<root>");
			Set<String> keys = this.keySet();
			for (String k : keys) {
				writer.write("<entry key=\"" + k + "\">" + this.get(k)
						+ "</entry>");
				writer.newLine();
			}
			writer.write("</root>");
			writer.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Returns true, if the config file could not be found.
	 * 
	 * @return A boolean.
	 */
	public boolean showConfigWindow() {
		return showConfigWindow;
	}

}
